/** <!--------------------------------------------------------------------------------------------->
 * @file GraphWindow.cpp
 *
 * GSvn - A Graphical Client for SVN
 * Copyright (C) 2012  Karl Krach, gsvn@bluespirit.la
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 <!---------------------------------------------------------------------------------------------> */


// -- includes --
#include "GraphWindow.h"
#include "ui_GraphWindow.h"
#include <QFileSystemModel>
#include <QDebug>
#include <subversion-1/svn_client.h>
#include <QTime>

/** <!--------------------------------------------------------------------------------------------->
 * Creates the GraphWindow object
 *
 * @param pParent pointer to parent object
 <!---------------------------------------------------------------------------------------------> */
GraphWindow::GraphWindow( QWidget* pParent ) : QMainWindow(pParent), ui(new Ui::GraphWindow)
{
	ui->setupUi(this);

	//
	// Initializes the tree view
	//
	QFileSystemModel* pFileSystemModel = new QFileSystemModel(this);
	pFileSystemModel->setRootPath( QDir::currentPath() );
	ui->m_pTreeView->setModel(pFileSystemModel);
	ui->m_pTreeView->setRootIndex( pFileSystemModel->index(QDir::currentPath()) );

	//
	// Hides the tree view
	//
	ui->m_pSplitter->setSizes( QList<int>() << 0 << 100 );

	InitSvn();

	ui->m_pGraphicsView->setScene( &m_oScene );

}	// END GraphWindow( QWidget* )

/** <!--------------------------------------------------------------------------------------------->
 * Deletes the GraphWindow object
 <!---------------------------------------------------------------------------------------------> */
GraphWindow::~GraphWindow()
{
	delete ui;

}	// END ~GraphWindow()



/* Version compatibility check */
static svn_error_t * check_lib_versions( void )
{
	static const svn_version_checklist_t checklist[] =
	{
		{ "svn_subr",   svn_subr_version },
		{ "svn_client", svn_client_version },
		{ "svn_wc",     svn_wc_version },
		{ "svn_ra",     svn_ra_version },
		{ "svn_delta",  svn_delta_version },
		{ "svn_diff",   svn_diff_version },
		{ NULL, NULL }
	};

	SVN_VERSION_DEFINE(my_version);
	return svn_ver_check_list(&my_version, checklist);
}

#include <subversion-1/svn_cmdline.h>
#include <subversion-1/svn_pools.h>
#include <subversion-1/svn_fs.h>
#include <apr_tables.h>
#include <apr_strings.h>
#include <apr_general.h>
#include <apr_signal.h>


/* Display a prompt and read a one-line response into the provided buffer,
   removing a trailing newline if present. */
static svn_error_t *
prompt_and_read_line(const char *prompt,
					 char *buffer,
					 size_t max)
{
	int len;
	printf("%s: ", prompt);
	if (fgets(buffer, max, stdin) == NULL)
		return svn_error_create(0, NULL, "error reading stdin");
	len = strlen(buffer);
	if (len > 0 && buffer[len-1] == '\n')
		buffer[len-1] = 0;
	return SVN_NO_ERROR;
}

svn_error_t* my_client_list_callback( void *baton,
										const char *path,
										const svn_dirent_t *dirent,
										const svn_lock_t *lock,
										const char *abs_path,
										apr_pool_t *pool)
{
	Q_UNUSED( pool );

	qDebug() << "my_client_list_callback called!";
	if( baton )
	{
		char* baton_text = static_cast<char*>( baton );
		qDebug() << "  baton_text=" << baton_text;
	}
	if( path )     qDebug() << "  path=" << path;
	if( dirent )   qDebug() << "  rev=" << dirent->created_rev;
	if( dirent )   qDebug() << "  author=" << dirent->last_author;
	if( dirent )   qDebug() << "  size=" << dirent->size;
	if( lock )     qDebug() << "  lock.comment=" << lock->comment;
	if( abs_path ) qDebug() << "  abs_path=" << abs_path;

	return SVN_NO_ERROR;
}

/* A tiny callback function of type 'svn_auth_simple_prompt_func_t'. For
   a much better example, see svn_cl__auth_simple_prompt in the official
   svn cmdline client. */
static svn_error_t *
my_simple_prompt_callback (svn_auth_cred_simple_t **cred,
						   void *baton,
						   const char *realm,
						   const char *username,
						   svn_boolean_t may_save,
						   apr_pool_t *pool)
{
	Q_UNUSED( baton );
	Q_UNUSED( may_save );

	svn_auth_cred_simple_t* ret = (svn_auth_cred_simple_t*)apr_pcalloc (pool, sizeof (*ret));
	char answerbuf[100];

	if (realm)
	{
		printf ("Authentication realm: %s\n", realm);
	}

	if (username)
		ret->username = apr_pstrdup (pool, username);
	else
	{
		SVN_ERR (prompt_and_read_line("Username", answerbuf, sizeof(answerbuf)));
		ret->username = apr_pstrdup (pool, answerbuf);
	}

	SVN_ERR (prompt_and_read_line("Password", answerbuf, sizeof(answerbuf)));
	ret->password = apr_pstrdup (pool, answerbuf);

	*cred = ret;
	return SVN_NO_ERROR;
}


/* A tiny callback function of type 'svn_auth_username_prompt_func_t'. For
   a much better example, see svn_cl__auth_username_prompt in the official
   svn cmdline client. */
static svn_error_t *
my_username_prompt_callback (svn_auth_cred_username_t **cred,
							 void *baton,
							 const char *realm,
							 svn_boolean_t may_save,
							 apr_pool_t *pool)
{
	Q_UNUSED(baton);
	Q_UNUSED(may_save);

	svn_auth_cred_username_t* ret = (svn_auth_cred_username_t*)apr_pcalloc (pool, sizeof (*ret));
	char answerbuf[100];

	if (realm)
	{
		printf ("Authentication realm: %s\n", realm);
	}

	SVN_ERR (prompt_and_read_line("Username", answerbuf, sizeof(answerbuf)));
	ret->username = apr_pstrdup (pool, answerbuf);

	*cred = ret;
	return SVN_NO_ERROR;
}



svn_error_t* MyLoggingCallback( void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool )
{
	QString strOutput;

	Q_UNUSED( baton );
	Q_UNUSED( pool );

	if( !log_entry )
	{
		qDebug() << "MyLoggingCallback: log_entry is NULL!";
		return SVN_NO_ERROR;
	}

	if( log_entry->revision == SVN_INVALID_REVNUM )
	{
		qDebug() << "END OF CHILDREN!";
		return SVN_NO_ERROR;
	}

	qDebug() << "MyLoggingCallback called for r" << log_entry->revision;

	strOutput += QString("[%1]").arg( log_entry->revision );
	if( log_entry->has_children ) strOutput = "+ " + strOutput;
	else strOutput = "  " + strOutput;

	//The hash of requested revision properties, which may be NULL if it would contain no revprops
	if( log_entry->revprops )
	{
		for( apr_hash_index_t*hi = apr_hash_first(pool, log_entry->revprops); hi; hi = apr_hash_next(hi) )
		{
			const char *key;
			svn_string_t *value;

			apr_hash_this(hi, (const void**)&key, NULL, (void**)&value);

			if( QString(key).contains("mergeinfo") )
				qDebug() << " ##################\n##################\n##################\n";

			qDebug() << strOutput.toAscii() << key << QString(value->data).replace( QRegExp("\\s"), " " );
		}
	}


	//  A hash containing as keys every path committed in revision; the values are
	// (svn_log_changed_path2_t *) stuctures.
	if (log_entry->changed_paths2)
	{
		for( apr_hash_index_t*hi = apr_hash_first(pool, log_entry->changed_paths2); hi; hi = apr_hash_next(hi) )
		{
			const char *key;
			svn_log_changed_path2_t* log_item;

			apr_hash_this(hi, (const void**)&key, NULL, (void**)&log_item);

			QString strLine = strOutput + " " + log_item->action + " " + key;



			if( log_item )
			{
				if (log_item->copyfrom_path && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
				{
					strLine += QString("  from %1:%2").arg(log_item->copyfrom_path)
													  .arg(log_item->copyfrom_rev);
				}
				switch( log_item->node_kind )
				{
					case svn_node_none:
						strLine += "  (absent)";
						break;
					case svn_node_file:
						strLine += "  (regular file)";
						break;
					case svn_node_dir:
						strLine += "  (directory)";
						break;
					case svn_node_unknown:
						strLine += "  -- something's here, but we don't know what";
						break;
				}
			}
			qDebug() << strLine.toAscii();
		}
	}

	return SVN_NO_ERROR;

}	// END MyLoggingCallback


static svn_error_t* MyCancelCallback(void *cancel_baton)
{
	Q_UNUSED( cancel_baton );

	qDebug() << "Cancel??";

	return SVN_NO_ERROR;
}

static void MyProgressCallback( apr_off_t progress,
                                              apr_off_t total,
                                              void *baton,
                                              apr_pool_t *pool)
{
	Q_UNUSED(baton);
	Q_UNUSED(pool);
	qDebug() << "MyProgressCallback: progress = " << progress << " total =" << total;
}

#include <subversion-1/svn_mergeinfo.h>
#include <subversion-1/svn_repos.h>

/** <!--------------------------------------------------------------------------------------------->
 * Initializes the SVN and fills the graphic scene with data
 <!---------------------------------------------------------------------------------------------> */
void GraphWindow::InitSvn()
{
	//const char* URL_OR_PATH = "src/GraphWindow.cpp";
	const char* URL_OR_PATH = "icons";


	qDebug() << "start InitSvn";
	svn_error_t* pError;
	m_oScene.addText( "Hello world" );


	// Initializes ARP, sets the locale and registers stderr for the error reporting
	if( svn_cmdline_init("gsvn", stderr) != EXIT_SUCCESS )
	{
		qWarning() << "Error in svn_cmdline_init!";
		return;
	}

	// Creates a root-pool with the default allocator
	apr_pool_t* pool = svn_pool_create (NULL);

	// Initialize the FS library for file access
	if( (pError = svn_fs_initialize (pool)) != NULL )
	{
		qWarning() << "Error while svn_fs_initialize!";

		svn_error_clear( pError );
		return svn_pool_destroy(pool);
	}

	//
	// Compare linked library version vs. dynamically loade ones
	//
	if( (pError = check_lib_versions()) != NULL )
	{
		qWarning() << "Version mismatch!";

		svn_error_clear( pError );
		return svn_pool_destroy(pool);
	}

	SVN_VERSION_DEFINE(  oBinVersion );
	const svn_version_t* pLibVersion = svn_subr_version();

	qDebug() << "Binary SVN Version: " << oBinVersion.major << oBinVersion.minor
	         << oBinVersion.patch << oBinVersion.patch;
	qDebug() << "SVN Library Version: " << pLibVersion->major << pLibVersion->minor
	         << pLibVersion->patch << pLibVersion->patch;

	// Initialize the repository access library
	/* Initialize the RA library. */
	if( (pError = svn_ra_initialize(pool)) != NULL )
	{
		qWarning() << "Error in svn_ra_initialize!";

		svn_error_clear( pError );
		return svn_pool_destroy(pool);
	}

	// Tests for the ~/.subversion directory and creates it otherwise with default values
	if( (pError = svn_config_ensure(NULL , pool)) != NULL )
	{
		qWarning() << "Error in svn_config_ensure!";

		svn_error_clear( pError );
		return svn_pool_destroy(pool);
	}

	// Initialize client context
	svn_client_ctx_t *ctx;
	if( (pError = svn_client_create_context(&ctx, pool)) != NULL )
	{
		qWarning() << "Error in svn_client_create_context!";

		svn_error_clear( pError );
		return svn_pool_destroy(pool);
	}

	// Read configuration information from the standard sources and merge it into the config hash
	pError = svn_config_get_config( &(ctx->config), NULL, pool);
	if( pError )
	{
		// Fallback to default config if the config directory isn't readable or is not a directory.
		if( APR_STATUS_IS_EACCES(pError->apr_err) || APR_STATUS_IS_ENOTDIR(pError->apr_err) )
		{
			qDebug() << "Warning: XXXX";
			svn_error_clear(pError);
		}
		else
		{
			qWarning() << "Error in svn_config_get_config!";

			svn_error_clear( pError );
			return svn_pool_destroy(pool);
		}
	}


	ctx->progress_func = MyProgressCallback;
	ctx->cancel_func = MyCancelCallback;


	#ifdef SIGPIPE
	/* Disable SIGPIPE generation for the platforms that have it. */
	apr_signal(SIGPIPE, SIG_IGN);
	#endif

	#ifdef SIGXFSZ
	/* Disable SIGXFSZ generation for the platforms that have it, otherwise
	 * working with large files when compiled against an APR that doesn't have
	 * large file support will crash the program, which is uncool. */
	apr_signal(SIGXFSZ, SIG_IGN);
	#endif

	/* Make the client_ctx capable of authenticating users */
	{
	  /* There are many different kinds of authentication back-end
		 "providers".  See svn_auth.h for a full overview.

		 If you want to get the auth behavior of the 'svn' program,
		 you can use svn_cmdline_setup_auth_baton, which will give
		 you the exact set of auth providers it uses.  This program
		 doesn't use it because it's only appropriate for a command
		 line program, and this is supposed to be a general purpose
		 example. */

	  svn_auth_provider_object_t *provider;
	  apr_array_header_t *providers = apr_array_make (pool, 4, sizeof (svn_auth_provider_object_t *));

	  svn_auth_get_simple_prompt_provider (&provider,
										   my_simple_prompt_callback,
										   NULL, /* baton */
										   2, /* retry limit */ pool);
	  APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider;

	  svn_auth_get_username_prompt_provider (&provider,
											 my_username_prompt_callback,
											 NULL, /* baton */
											 2, /* retry limit */ pool);
	  APR_ARRAY_PUSH (providers, svn_auth_provider_object_t *) = provider;

	  /* Register the auth-providers into the context's auth_baton. */
	  svn_auth_open (&ctx->auth_baton, providers, pool);
	}

	// Postpone conflicts
	ctx->conflict_func  = NULL;
	ctx->conflict_baton = NULL;


#ifdef GET_LOG

	qDebug();
	qDebug() << "-------------------------svn_client_url_from_path--------------------------------";

	qDebug();
	{
		const char* URL;
		const char* path = URL_OR_PATH;
		pError = svn_client_url_from_path( &URL, path, pool );
		if( pError )
		{
			qDebug() << "Error in svn_client_url_from_path:" << pError->message << pError->file << pError->line;
			svn_error_clear( pError );
			return svn_pool_destroy(pool);
		}

		qDebug() << "path=" << path << " --> URL=" << URL;
	}
	qDebug();
	qDebug();
	qDebug() << "----------------------svn_client_root_url_from_path----------------------------";
	qDebug();
	{
		const char* URL;
		const char* path = URL_OR_PATH;
		pError = svn_client_root_url_from_path( &URL, path, ctx, pool );
		if( pError )
		{
			qDebug() << "Error in svn_client_root_url_from_path:" << pError->message << pError->file << pError->line;
			svn_error_clear( pError );
			return svn_pool_destroy(pool);
		}

		qDebug() << "path=" << path << " --> root-URL=" << URL;
	}
	qDebug();
	qDebug();
	qDebug() << "----------------------svn_client_mergeinfo_get_merged----------------------------";
	qDebug();

	svn_mergeinfo_t mergeinfo;
	const char *path_or_url = URL_OR_PATH;
//	const char *path_or_url = "svn://localhost/testproject/trunk";
//	const char *path_or_url = "svn://localhost/testproject/branches/release-1.x";
//	const char *path_or_url = "svn://localhost/testproject/branches/work_route";
//	const char *path_or_url = "https://gsvn.googlecode.com/svn/branches/";
	svn_opt_revision_t peg_revision;
	peg_revision.kind = svn_opt_revision_unspecified;
//	peg_revision.kind = svn_opt_revision_number;
//	peg_revision.value.number = 13;

	pError = svn_client_mergeinfo_get_merged( &mergeinfo, path_or_url, &peg_revision, ctx, pool );


	if( pError != NULL )
	{
		qDebug() << "Error in svn_client_mergeinfo_get_merged:" << pError->message << pError->file << pError->line;
		svn_error_clear( pError );
		return svn_pool_destroy(pool);
	}

	if( mergeinfo == NULL )
	{
		qDebug() << "mergeinfo is NULL!";
	}
	else
	{

		for( apr_hash_index_t* hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi) )
		{
			const char* path;
			apr_array_header_t* rangeList;
			apr_hash_this(hi, (const void**)&path, NULL, (void**)&rangeList);

			qDebug() << "Merges for " << path << ":";
			for( int iCnt=0; iCnt < rangeList->nelts; iCnt++ )
			{
				svn_merge_range_t* hugo = APR_ARRAY_IDX( rangeList, iCnt, svn_merge_range_t*);

				qDebug() << "  -> start=" << hugo->start << " stop=" << hugo->end << " inh=" << hugo->inheritable;
			}
		}
	}

#else
//	{
//		svn_mergeinfo_catalog_t *catalog;
//		svn_repos_t *repos;
//		const apr_array_header_t *paths;
//		svn_revnum_t revision;
//		svn_mergeinfo_inheritance_t inherit;
//		svn_boolean_t include_descendants;
//		svn_repos_authz_func_t authz_read_func;
//		void *authz_read_baton;
//		apr_pool_t *pool;

//		svn_repos_fs_get_mergeinfo();

//	}


	URL_OR_PATH = "https://gsvn.googlecode.com/svn";
	URL_OR_PATH = "svn://localhost/testproject/branches/release-1.x";
	URL_OR_PATH = "svn://localhost/testproject/trunk/hugo.txt";
	URL_OR_PATH = "svn://localhost/testproject/branches/";
	URL_OR_PATH = "svn://localhost/testproject/branches/work_route/hugo.txt";

	//
	// Defining file to get history from
	//
	apr_array_header_t* targets = apr_array_make( pool, 1, sizeof(const char*) );
	APR_ARRAY_PUSH (targets, const char*) = URL_OR_PATH;

	//
	// The peg_revision is the revision of the URL_OR_PATH (it is needed, since this file can be
	// renamed in another revision!) Use svn_opt_revision_unspecified here, to have "working" for
	// paths and "head" for URLs.
	//
	svn_opt_revision_t peg_revision;
//	peg_revision.kind = svn_opt_revision_unspecified;
	peg_revision.kind = svn_opt_revision_number;
	peg_revision.value.number = 13;

	//
	// Defining a revision range from rev0 to HEAD
	//
	apr_array_header_t* revision_ranges = apr_array_make( pool, 1, sizeof(svn_opt_revision_range_t*) );
	svn_opt_revision_range_t* revision_range = (svn_opt_revision_range_t*)apr_pcalloc( pool, sizeof (svn_opt_revision_range_t) );
	revision_range->start.kind = svn_opt_revision_head;
	revision_range->start.value.number = 0;
	revision_range->end.kind = svn_opt_revision_number;
	revision_range->end.value.number = 0;
	APR_ARRAY_PUSH (revision_ranges, const svn_opt_revision_range_t*) = revision_range;


	// Get all logs (not only the first 'limit' ones)
	int limit = 10;

	// If discover_changed_paths is set, then the `changed_paths' argument to receiver will be
	// passed on each invocation.
	svn_boolean_t discover_changed_paths = true;

	// If strict_node_history is set, copy history (if any exists) will not be traversed while
	// harvesting revision logs for each target.
	svn_boolean_t strict_node_history = false;

	// If include_merged_revisions is set, log information for revisions which have been merged to
	// targets will also be returned.
	svn_boolean_t include_merged_revisions = true;

	// If revprops is NULL, retrieve all revprops; else, retrieve only the revprops named in the
	// array (i.e. retrieve none if the array is empty).
	const apr_array_header_t *revprops = NULL;

	svn_log_entry_receiver_t receiver = MyLoggingCallback;
	void *receiver_baton = (void*) "huhu";

	qDebug() << " --------- Start svn_client_log --------- ";
	QTime oTimer;
	oTimer.start();
	//
	// Call CLIENT_METHOD here
	//
	pError = svn_client_log5( targets, &peg_revision, revision_ranges, limit, discover_changed_paths,
	                          strict_node_history, include_merged_revisions, revprops, receiver,
	                          receiver_baton, ctx, pool);
	qDebug() << " --------- Finish svn_client_log5 after " << oTimer.elapsed() << "ms --------- ";
	qDebug() << URL_OR_PATH;

	//
	// Check for errors
	//
	if( pError != NULL )
	{
		qDebug() << "Error in svn_client_list2:" << pError->message << pError->file << pError->line;
		svn_error_clear( pError );
		return svn_pool_destroy(pool);
	}
	qDebug() << "terminate InitSvn";
#endif
	return svn_pool_destroy(pool);

}	// END InitSvn()























